#define SVD
using System;
using System.Globalization;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;
using System.ComponentModel;

using glue.Extensions.Enumerable;
using glue.XDag;
using Matrix = glue.Math.Matrix.Matrix;

namespace glue.WpfUtil
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public partial class DagLayoutPanel : Panel
	{

		class LayoutDag : Dag<LayoutDag.Node>
		{
			static Size infinite_size = new Size(double.PositiveInfinity, double.PositiveInfinity);

			[DebuggerDisplay("{ToString(),nq}")]
			public new class Node : Dag<LayoutDag.Node>.Node, INotifyPropertyChanged
			{
				[DebuggerBrowsable(DebuggerBrowsableState.Never)]
				Point top_handle;

				[DebuggerBrowsable(DebuggerBrowsableState.Never)]
				Point bot_handle;

				//[DebuggerBrowsable(DebuggerBrowsableState.Never)]
				Rect r_cur;

				[DebuggerBrowsable(DebuggerBrowsableState.Never)]
				readonly public FrameworkElement fe;

				public int order;

				static int i_next = 0;

				public enum LayoutMode
				{
					Fixed = 0,
					ArrangeToParents = 1,
					ArrangeToChildren = 2,
				}

				public Node(LayoutDag ld, FrameworkElement fe)
					: base(ld)
				{
					this.order = i_next++;
					this.fe = fe;
					fe.Measure(infinite_size);
				}

				public void SetFixedLayout(Point pt)
				{
					r_cur = new Rect(pt, DesiredSize);
				}

				public void PrepareLayout()
				{
					if (Parents.Count > 0)
					{
						foreach (Node par in Parents)
							par.PropertyChanged += new PropertyChangedEventHandler(ParentPropertyChanged);
					}
					if (Children.Count > 0)
					{
						foreach (Node n in Children)
							n.PropertyChanged += new PropertyChangedEventHandler(ChildPropertyChanged);
					}
				}

				public void UpdateLayout()
				{
					if (Children.Count > 0 && Children.All(c => c.Parents.Count == 1))
					{
						foreach (Node n in Children)
							n.CurrentLayout = GetChildProposal(n);
					}
					else if (Parents.Count > 0 && Parents.All(c => c.Children.Count == 1))
					{
						foreach (Node n in Parents)
							n.CurrentLayout = GetParentProposal(n);
					}
					else
					{

					}
				}


				Rect[] child_proposals = null;
				Rect[] ChildProposals
				{
					get
					{
						if (child_proposals == null)
						{
							Node[] rg = OrderedChildren.ToArray();
							child_proposals = new Rect[rg.Length];
							double dx = 0;

							int i = 0;
							foreach (Node c in rg)
							{
			//					if (c.Level == Level + 1)
								{
									if (dx > 0)
										dx += 10;
									Rect cp = new Rect(new Point(dx, c.Level * 70), c.DesiredSize);
									child_proposals[i] = cp;
									dx += cp.Width;
								}
								i++;
							}

							double w = r_cur.Left + (r_cur.Width / 2) - (dx / 2);

							i = 0;
							foreach (Node c in rg)
							{
		//						if (c.Level == Level + 1)
									child_proposals[i].X += w;
								i++;
							}
						}
						return child_proposals;
					}
				}

				Rect GetChildProposal(Node child)
				{
					Debug.Assert(Children.Count > 0);
					return ChildProposals[GetChildIndex(child)];
				}

				Rect[] parent_proposals = null;
				Rect[] ParentProposals
				{
					get
					{
						if (parent_proposals == null)
						{
							Node[] rg = OrderedParents.ToArray();
							parent_proposals = new Rect[rg.Length];
							double dx = 0;

							int i = 0;
							foreach (Node p in rg)
							{
				//				if (p.Level == Level - 1)
								{
									if (dx > 0)
										dx += 10;
									Rect cp = new Rect(new Point(dx, p.Level * 70), p.DesiredSize);
									parent_proposals[i] = cp;
									dx += cp.Width;
								}
								i++;
							}

							double w = r_cur.Left + ((r_cur.Width / 2) - (dx / 2));

							i = 0;
							foreach (Node p in rg)
							{
			//					if (p.Level == Level - 1)
									parent_proposals[i].X += w;
								i++;
							}
						}
						return parent_proposals;
					}
				}

				Rect GetParentProposal(Node parent)
				{
					Debug.Assert(Parents.Count > 0);
					return ParentProposals[GetParentIndex(parent)];
				}

				IEnumerable<Node> OrderedChildren
				{
					get { return Children.OrderBy(n => n.order); }
				}
				IEnumerable<Node> OrderedParents
				{
					get { return Parents.OrderBy(n => n.order); }
				}
				int GetChildIndex(Node child)
				{
					return OrderedChildren.IndexOfFirst(n => n == child);
				}
				int GetParentIndex(Node parent)
				{
					return OrderedParents.IndexOfFirst(n => n == parent);
				}

				public Rect CurrentLayout
				{
					get
					{
						return r_cur;
					}
					set
					{
						if (f_changing_property)
							return;
						if (!value.Equals(r_cur) && !value.Equals(default(Rect)))
						{
							r_cur = value;
							child_proposals = null;
							parent_proposals = null;
							NotifyPropertyChanged("CurrentLayout");
						}
					}
				}

				//double par_min;
				//double par_max;

				void ParentPropertyChanged(object sender, PropertyChangedEventArgs e)
				{
					if (!f_changing_property && e.PropertyName == "CurrentLayout")
					{
						Node parent = (Node)sender;
						//CurrentLayout = parent.GetChildProposal(this);
						UpdateLayout();
					}
				}
				void ChildPropertyChanged(object sender, PropertyChangedEventArgs e)
				{
					if (!f_changing_property && e.PropertyName == "CurrentLayout")
					{
						Node child = (Node)sender;
						//CurrentLayout = child.GetParentProposal(this);
						UpdateLayout();
					}
				}

				public Size DesiredSize { get { return fe.DesiredSize; } }

				public FrameworkElement Item { get { return fe; } }

				public Rect Final
				{
					get
					{
						Rect r_final;
						Size desired = DesiredSize;

						r_final = r_cur;

						top_handle = r_final.TopLeft;
						top_handle.X += r_final.Width / 2;

						bot_handle = r_final.BottomLeft;
						bot_handle.X += r_final.Width / 2;

						return r_final;
					}
				}

				public void DoRender(DrawingContext dc, Pen pen, Pen dashpen)
				{
					foreach (var cn in Children)
					{
						if (!cn.top_handle.Equals(default(Point)))
							dc.DrawLine(pen, bot_handle, cn.top_handle);
					}
				}

				public new LayoutDag Dag { get { return (LayoutDag)base.Dag; } }

				public override string ToString()
				{
					return String.Format("{0} {1} parents: {2} children: {3}", fe.Name, Parents.Count, Children.Count);
				}

				bool f_changing_property = false;
				void NotifyPropertyChanged(String s_field)
				{
					var h = PropertyChanged;
					if (h != null)
					{
						f_changing_property = true;
						h(this, new PropertyChangedEventArgs(s_field));
						f_changing_property = false;
					}
				}

				public event PropertyChangedEventHandler PropertyChanged;
			};

		};


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if false
		protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
		{
			base.OnVisualChildrenChanged(visualAdded, visualRemoved);

			FrameworkElement node = visualAdded as FrameworkElement;
			if (node != null)
			{
				dag2.AddNode(node);
				IList parents = node.GetValue(DagParentsProperty) as IList;
				if (parents != null)
					foreach (FrameworkElement parent in parents.OfType<FrameworkElement>())
						dag2.AddParent(node, parent);

				if (node.Visibility == Visibility.Hidden)
				{
					dag2.SetNodeCollapsed(node, true);
				}
			}

			node = visualRemoved as FrameworkElement;
			if (node != null)
			{
				foreach (FrameworkElement p in dag2.Parents(node))
					dag2.RemoveParent(node, p);
				foreach (FrameworkElement c in dag2.Children(node))
					dag2.RemoveChild(node, c);
				///...
				///delete node
			}
		}
#endif

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override void OnChildDesiredSizeChanged(UIElement child)
		{
			base.OnChildDesiredSizeChanged(child);
		}

		static IEnumerable<FrameworkElement> VisibleParents(FrameworkElement node)
		{
			HashSet<FrameworkElement> hs = new HashSet<FrameworkElement>();
			GetVisibleParents(hs, node);
			return hs;
		}

		static void GetVisibleParents(HashSet<FrameworkElement> found, FrameworkElement node)
		{
			IList parents = node.GetValue(DagParentsProperty) as IList;
			if (parents == null)
				return;
			foreach (FrameworkElement parent in parents.OfType<FrameworkElement>())
			{
				if (parent.Visibility == Visibility.Hidden)
					GetVisibleParents(found, parent);
				else
					found.Add(parent);
			}
		}

		Double y_spacing = 70;
		LayoutDag dag;
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override Size MeasureOverride(Size availableSize)
		{
			FrameworkElement[] children = InternalChildren
											.OfType<FrameworkElement>()
											.Where(fe => fe.Visibility == Visibility.Visible)
											.ToArray();
			if (children.Length == 0)
				return new Size(100, 100);

			dag = new LayoutDag();

			Dictionary<FrameworkElement, LayoutDag.Node> layouts = new Dictionary<FrameworkElement, LayoutDag.Node>();

			foreach (var fe in children)
			{
				LayoutDag.Node ln = new LayoutDag.Node(dag, fe);
				layouts.Add(fe, ln);
			}

			foreach (var kvp in layouts)
			{
				FrameworkElement fe = kvp.Key;
				LayoutDag.Node node = kvp.Value;

				foreach (FrameworkElement fe_par in VisibleParents(fe))
				{
					LayoutDag.Node n_par = layouts[fe_par];
					node.AddParent(n_par);
				}
			}


#if false
			//			var tn = dag
			//			.Where(n => n.Parents.Count == 1 && n.Children.Count > 0 && n.Children.All(ch => ch.IsLeaf))
			//		.ToArray();

			//			Debug.Print("removing {0} children from {1} nodes", tn.Sum(n => n.Children.Count), tn.Length);
			List<LayoutDag.Node> to_remove = new List<LayoutDag.Node>();

			foreach (var zzz in dag)
			{
				foreach (var xyz in zzz.Children)
				{
					if (xyz.IsLeaf && xyz.Parents.Count == 1)
					{
						zzz.leaves.Add(xyz.Item);
						to_remove.Add(xyz);
					}
				}
			}
			Debug.Print("removing {0} child nodes", to_remove.Count);
			foreach (var rn in to_remove)
				dag.Remove(rn);
#endif

			double x = 0;
#if SVD
			DagLayoutCalc dlc = new DagLayoutCalc(this);
			dlc.ComputeSingularValueDecomposition(1);

			double[] lx = dlc.B.Row(0);
			int j = 0;
			foreach (int i in lx.Select((v, ix) => new { v, ix }).OrderBy(a => a.v).Select(a => a.ix))
			{
				dag[i].order = j++;
			}

			LayoutDag.Node[][] level_nodes = lx.Select((v, ix) => new { v, node = dag[ix] })
												.GroupBy(a => a.node.Level)
												.OrderBy(g => g.Key)
												.Select(g => g.OrderBy(a => a.v).Select(a => a.node).ToArray())
												.ToArray();

			int lv_most = level_nodes.IndexOfMax(rg => rg.Length);

			foreach (var lix in level_nodes)
			{
				//				if (li.Level == lv_most)
				x = 0;
				foreach (var li in lix)
				{
					//li.Final = ;
					li.SetFixedLayout(new Point(x, li.Level * y_spacing));
					x += (int)li.DesiredSize.Width + 10;
				}
				//else if (li.Children.Count == 0)
				//{
				//    //li.lm = LayoutDag.Node.LayoutMode.ArrangeToParents;
				//}
				//else if (li.Parents.Count == 0)
				//{
				//    //li.lm = LayoutDag.Node.LayoutMode.ArrangeToChildren;
				//}
				//else if (li.Children.All(ch => ch.Parents.Count == 1))
				//{
				//    li.lm = LayoutDag.Node.LayoutMode.ArrangeToChildren;
				//}
				//else if (li.Parents.All(p => Children.Count == 1))
				//{
				//    li.lm = LayoutDag.Node.LayoutMode.ArrangeToParents;
				//}
				//else if (li.Level < lv_most)
				//{
				//    li.lm = LayoutDag.Node.LayoutMode.ArrangeToChildren;
				//}
				//else if (li.Level > lv_most)
				//{
				//    li.lm = LayoutDag.Node.LayoutMode.ArrangeToParents;
				//}
			}
#else
			int[] level_x = new int[dag.MaxLevel + 1];

			foreach (var li in dag)
			{
				Size desired = li.DesiredSize;

				x = level_x[li.Level];
				if (x > 0)
					x += 10;

				li.SetFixedLayout(new Point(x, li.Level * y_spacing));

				level_x[li.Level] = (int)li.Final.Right;
			}
#endif
			foreach (var n in dag)
			{
				n.PrepareLayout();
			}

			foreach (var n in dag)
			{
				n.UpdateLayout();
			}
#if SVD
#if false
			int lv_cur = lv_most;
			bool f_any;
			do
			{
				f_any = false;
				while (--lv_cur >= 0)
				{
					foreach (var li in level_nodes[lv_cur].Where(n => n.Final.Equals(default(Rect))))
					{
						Size desired = li.DesiredSize;
						LayoutDag.Node[] rgnln;
						if (li.lm == LayoutDag.Node.LayoutMode.ArrangeToChildren)
							rgnln = li.Children.ToArray();
						else
							rgnln = li.Parents.ToArray();

						if (rgnln.Length > 0)
						{
							double x0 = rgnln.Min(nln => nln.Final.Left);
							double x1 = rgnln.Max(nln => nln.Final.Right);
							x = x0 + ((x1 - x0) / 2 - desired.Width / 2);
							if (x > 0)
							{
								li.Final = new Rect(new Point(x, li.Level * y_spacing), desired);
								f_any = true;
							}
							else
							{
								glue.Debugging.Nop.X();
							}
						}
					}
				}
				lv_cur = dag.MaxLevel + 1;
			}
			while (f_any);
#endif
#if false


			int lv_cur = lv_most;
			while (--lv_cur >= 0)
			{
				foreach (var li in level_nodes[lv_cur].Where(n => n.lm == LayoutDag.Node.LayoutMode.ArrangeToChildren))
				{
					Size desired = li.DesiredSize;
					var rgnln = li.Children.ToArray();
					if (rgnln.Length > 0)
					{
						double x0 = rgnln.Min(nln => nln.Final.Left);
						double x1 = rgnln.Max(nln => nln.Final.Right);
						x = x0 + ((x1 - x0) / 2 - desired.Width / 2);
						li.Final = new Rect(new Point(x, li.Level * y_spacing), desired);
					}
					else
						Debugger.Break();
				}
			}
			while (++lv_cur < lv_most)
			{
				foreach (var li in level_nodes[lv_cur].Where(n => n.lm == LayoutDag.Node.LayoutMode.ArrangeToParents))
				{
					Size desired = li.DesiredSize;
					var rgnln = li.Parents.ToArray();
					if (rgnln.Length > 0)
					{
						double x0 = rgnln.Min(nln => nln.Final.Left);
						double x1 = rgnln.Max(nln => nln.Final.Right);
						x = x0 + ((x1 - x0) / 2 - desired.Width / 2);
						li.Final = new Rect(new Point(x, li.Level * y_spacing), desired);
					}
					else
						Debugger.Break();
				}
			}


			while (++lv_cur <= dag.MaxLevel)
			{
				foreach (var li in level_nodes[lv_cur].Where(n => n.lm == LayoutDag.Node.LayoutMode.ArrangeToParents))
				{
					Size desired = li.DesiredSize;
					var rgpln = li.Parents.ToArray();
					if (rgpln.Length > 0)
					{
						double x0 = rgpln.Min(nln => nln.Final.Left);
						double x1 = rgpln.Max(nln => nln.Final.Right);
						x = x0 + ((x1 - x0) / 2 - desired.Width / 2);
						li.Final = new Rect(new Point(x, li.Level * y_spacing), desired);
					}
					else
						Debugger.Break();
				}
			}
			while (--lv_cur > lv_most)
			{
				foreach (var li in level_nodes[lv_cur].Where(n => n.lm == LayoutDag.Node.LayoutMode.ArrangeToChildren))
				{
					Size desired = li.DesiredSize;
					var rgpln = li.Children.ToArray();
					if (rgpln.Length > 0)
					{
						double x0 = rgpln.Min(nln => nln.Final.Left);
						double x1 = rgpln.Max(nln => nln.Final.Right);
						x = x0 + ((x1 - x0) / 2 - desired.Width / 2);
						li.Final = new Rect(new Point(x, li.Level * y_spacing), desired);
					}
					else
						Debugger.Break();
				}
			}
#endif

#endif
			double x_min = dag.Min(n => n.Final.Left);
			double x_max = dag.Max(n => n.Final.Right);
			double y_min = 0;
			double y_max = dag.Max(n => n.Final.Bottom);
			//foreach (var li in dag)
			//{
			//    if (li.Final.Right > x_max)
			//        x_max = li.Final.Right;
			//    if (li.Final.Bottom > y_max)
			//        y_max = li.Final.Bottom;
			//}
			RenderTransform = new TranslateTransform(-x_min, 0);
			return new Size(x_max - x_min, y_max);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override Size ArrangeOverride(Size finalSize)
		{
			if (dag == null)
				return new Size(100, 100);
			foreach (var li in dag)
			{
				li.Item.Arrange(li.Final);
			}
			return finalSize;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override void OnRender(DrawingContext dc)
		{
			base.OnRender(dc);

			if (dag == null)
				return;

			Pen pen = new Pen(Stroke, StrokeThickness);

			Pen dashpen = new Pen(Stroke, StrokeThickness);
			dashpen.DashStyle = DashStyles.Dash;

			foreach (var li in dag)
			{
				li.DoRender(dc, pen, dashpen);
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		class DagLayoutCalc
		{
			DagLayoutPanel panel;

			public Matrix B;

			public DagLayoutCalc(DagLayoutPanel panel)
			{
				this.panel = panel;
			}

			public void ComputeSingularValueDecomposition(int n_reduce_dims)
			{
				Matrix A = new double[panel.dag.Count, panel.dag.Count];

				int i = 0;
				foreach (var node in panel.dag)
				{
					double[] v = new double[panel.dag.Count];

					foreach (var pnode in node.Parents)
					{
						v[pnode.Index] = 1;// panel.dag.MaxLevel - (li.level - li2.level);
					}

					v[node.Index] = 2;// panel.dag.MaxLevel;

					foreach (var cnode in node.Children)
					{
						v[cnode.Index] = 1;// panel.dag.MaxLevel - (li2.level - li.level);
					}

					A.SetColumnValues(i, v);
					i++;
				}

				Debug.Print("Starting Singular Value Decomposition for matrix A[{0},{1}]", A.RowCount, A.ColumnCount);
				Stopwatch stopwatch = Stopwatch.StartNew();

				double[] w;
				Matrix VV;
				{
					Matrix V;
					Matrix.SingularValueDecomposition(A, out w, out V);
					Debug.Print("SVD complete in {0} ms.", stopwatch.Elapsed.TotalMilliseconds);

					VV = n_reduce_dims < V.RowCount ? V.TakeKRows(n_reduce_dims) : V;
				}

				/// populate w (the singular values) to the diagonal of a new square matrix and multiply by the first n rows of V.
				B = new Matrix(w, n_reduce_dims) * VV;

				Debug.Print("Dimensionality reduction to B[{0},{1}] complete.", B.RowCount, B.ColumnCount);
			}
		};
	};
}